#include <linux/kernel.h>
#include <linux/dcache.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/pagemap.h> 
#include <linux/fs.h>      
#include <asm/atomic.h>
#include <asm/uaccess.h>   


MODULE_LICENSE("GPL");

#define LFS_MAGIC 0x19980122
#define TMPSIZE 20

static inline unsigned int blksize_bits(unsigned int size)
{
	unsigned int bits = 8;
 	do {
 	bits++;
 	size >>= 1;
 	} while (size > 256);
 	return bits;
}


/*create inode for a new file or directory*/
static struct inode *make_inode(struct super_block *sb, int mode)
{
	struct inode *ret = new_inode(sb);

	if (ret) {
		ret->i_mode = mode;
		ret->i_uid = ret->i_gid = 0;
		ret->i_blkbits = blksize_bits(PAGE_CACHE_SIZE);
		ret->i_blocks = 0;
		ret->i_atime = ret->i_mtime = ret->i_ctime = CURRENT_TIME;
	}
	return ret;
}


/**File operation functions**/

/*file open*/
static int open_file(struct inode *inode, struct file *filp)
{
	filp->private_data = inode->i_private;
	return 0;
}

/*read file*/
static ssize_t read_file(struct file *filp, char *buf,
		size_t count, loff_t *offset)
{
	atomic_t *counter = (atomic_t *) filp->private_data;
	int v, len;
	char tmp[TMPSIZE];
/*
 * Encode the value, and figure out how much of it we can pass back.
 */
	v = atomic_read(counter);
	if (*offset > 0)
		v -= 1;  /* the value returned when offset was zero */
	else
		atomic_inc(counter);
	len = snprintf(tmp, TMPSIZE, "%d\n", v);
	if (*offset > len)
		return 0;
	if (count > len - *offset)
		count = len - *offset;
/*
 * Copy it back, increment the offset, and we're done.
 */
	if (copy_to_user(buf, tmp + *offset, count))
		return -EFAULT;
	*offset += count;
	return count;
}

/*write file*/
static ssize_t write_file(struct file *filp, const char *buf,
		size_t count, loff_t *offset)
{
	atomic_t *counter = (atomic_t *) filp->private_data;
	char tmp[TMPSIZE];
/*
 * Only write from the beginning.
 */
	if (*offset != 0)
		return -EINVAL;
/*
 * Read the value from the user.
 */
	if (count >= TMPSIZE)
		return -EINVAL;
	memset(tmp, 0, TMPSIZE);
	if (copy_from_user(tmp, buf, count))
		return -EFAULT;
/*
 * Store it in the counter and we are done.
 */
	atomic_set(counter, simple_strtol(tmp, NULL, 10));
	return count;
}


//File operations
static struct file_operations file_ops = {
	.open	= open_file,
	.read 	= read_file,
	.write  = write_file,
};

//implement socket call here
struct dentry *fs_lookup( struct inode *parent_inode, struct dentry *dentry ) 
{
	printk( "fs: inode_operations.lookup called with dentry \n");
	return NULL;
}

int fs_readdir( struct file *file, void *dirent, filldir_t filldir ) 
{
	printk( "fs: file_operations.readdir called\n" );
    return 0;
}

static struct dentry *lfs_create_file (struct super_block *sb,
		struct dentry *dir, const char *name,
		atomic_t *counter)
{
	struct dentry *dentry;
	struct inode *inode;
	struct qstr qname;

/*
 * Make a hashed version of the name to go with the dentry.
 */
	qname.name = name;
	qname.len = strlen (name);
	qname.hash = full_name_hash(name, qname.len);
	dentry = d_alloc(dir, &qname);
	if (! dentry)
		goto out;

	inode = make_inode(sb, S_IFREG | 0777);
	if (! inode) 
		goto out_dput;
	inode->i_fop = &file_ops; //setting file operations of inode to user defined function.
	inode->i_private = counter;
	//inode->u.generic_ip = counter;
/*
 * Put it all into the dentry cache and we're done.
 */
	d_add(dentry, inode);
	return dentry;
/*
 * Then again, maybe it didn't work.
 */
  out_dput:
	dput(dentry);
  out:
	return 0;
}


int create_inode (struct inode *parent_inode,struct dentry *dir, umode_t mode, bool flag)
{
	printk( "fs: create called\n" );
	

	return 0;
}

static struct super_operations fs_s_ops = {
	.statfs		= simple_statfs,
	.drop_inode	= generic_delete_inode,
};

struct inode_operations fs_iops = {
    	lookup: fs_lookup,
	//lookup: simple_lookup,
	create: create_inode
};

struct file_operations fs_dir_fops = {
    read   : generic_read_dir,
    readdir: &fs_readdir
};


//This function loads superblock
static int fill_super (struct super_block *sb, void *data, int silent)
{
	struct inode *root;
	struct dentry *root_dentry;

	//Superblock setup
	sb->s_blocksize = PAGE_CACHE_SIZE;
	sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
	sb->s_magic = LFS_MAGIC;
	sb->s_op = &fs_s_ops;


	//creating inode for root directory
	root = make_inode (sb, S_IFDIR | 0777);
	if (! root)
		goto out;
	root->i_op = &fs_iops;
	//root->i_fop = &fs_dir_fops ;
	//root->i_op = &simple_dir_inode_operations; //This sets the functions for inode operations
	root->i_fop = &simple_dir_operations; //This sets function names for directory operations
	
	//root_dentry = d_make_root(root);
	root_dentry = d_alloc_root(root);
	if (! root_dentry)
		goto out_iput;
	sb->s_root = root_dentry;

	//Creating file inside root directory
	//static atomic_t counter;
	//atomic_set(&counter, 0);
	//lfs_create_file(sb, root_dentry, "geethu", &counter);

	return 0;
	
  out_iput:
	iput(root);
  out:
	return -ENOMEM;
}

static struct super_block *newfsget_super(struct file_system_type *fst,
		int flags, const char *devname, void *data, struct vfsmount *mount)
{
	//return mount_single(fst, flags, data, fill_super);
	return get_sb_single(fst, flags, data, fill_super, mount);
}

static struct file_system_type fs_type = {
	.owner 		= THIS_MODULE,
	.name		= "cmpe142",
	//.mount		= newfsget_super,
	.get_sb		= newfsget_super,
	.kill_sb	= kill_litter_super,
};


static int __init newfs_init(void)
{
	return register_filesystem(&fs_type);
}

static void __exit newfs_exit(void)
{
	unregister_filesystem(&fs_type);
}

module_init(newfs_init);
module_exit(newfs_exit);

